package baidu.hudson.plugin.result.collector;

import baidu.hudson.plugin.result.collector.CCheckExtend.CCheckExtendedBuildAnalyzer;
import baidu.hudson.plugin.result.collector.CCheckExtend.InformationAbstractor;
import baidu.hudson.plugin.result.collector.MessageRouter.MessageSender;
import baidu.hudson.plugin.result.collector.MessageRouter.MessageSenderBuilder;
import baidu.hudson.plugin.result.collector.utils.BuildAnalyzer;
import baidu.hudson.plugin.result.collector.utils.JsonUtils;
import baidu.hudson.plugin.result.collector.utils.LocalCache;
import hudson.Launcher;
import hudson.Util;
import hudson.model.*;
import hudson.model.labels.LabelAtom;
import hudson.scm.SubversionChangeLogSet;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import hudson.tasks.Builder;
import hudson.triggers.SCMTrigger;
import hudson.triggers.TimerTrigger;
import hudson.util.TagCloud;
import org.apache.camel.CamelContext;
import org.apache.camel.ProducerTemplate;
import org.kohsuke.stapler.DataBoundConstructor;
import org.springframework.jms.JmsException;
import org.springframework.jms.UncategorizedJmsException;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.ConnectException;
import java.util.*;
import java.util.concurrent.Future;

/**
 * Sample {@link Builder}.
 * <p/>
 * <p/>
 * there is the hudson publisher which will be execute after a build , in the publisher have some
 * process for transfer the result to the remote server .
 * <p/>
 * When a build is performed, the {@link #perform(AbstractBuild, Launcher, BuildListener)} method
 * will be invoked.
 *
 * @author Kohsuke Kawaguchi
 */
public class ResultPublisher extends Notifier {

    private String hudsonUrl;
    private String teamFoundation;
    private String icafeProject;
    private String icafeModule;
    private String icafeVersion;
    private String viewUrl;
    private String viewName;
    private String projectType;
    private String localServername;
    private boolean doSync;
    private CCheckExtendedBuildAnalyzer buildAnalyzer;

    private final static String exceptionString = "error! sending failed , " +
            "it maybe caused by unreachable the remote server or other exception , " +
            "please ensure the config is correct , if the problem is still there , please contact the developer at yuxinlei@baidu.com .";
    private final static String jsonExceptionString = "warning ! some errors happen during converting object to json. ";

    // Fields in config.jelly must match the parameter names in the "DataBoundConstructor"
    @DataBoundConstructor
    public ResultPublisher(String localServername, String hudsonUrl, String teamFoundation, String hudsonSlave,
                           String icafeProject, String icafeModule, String icafeVersion, String viewUrl, String viewName,
                           String projectType, String projectUrl, boolean doSync) {

        this.setHudsonUrl(hudsonUrl);
        this.setTeamFoundation(teamFoundation);
        this.setIcafeProject(icafeProject);
        this.setIcafeModule(icafeModule);
        this.setIcafeVersion(icafeVersion);
        this.setViewUrl(viewUrl);
        this.setViewName(viewName);
        this.setProjectType(projectType);
        this.setLocalServername(localServername);
        this.setDoSync(doSync);


    }


    @Override
    public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException {
        listener.getLogger().println("start to collect the build result to the remote server .");
        buildAnalyzer = new CCheckExtendedBuildAnalyzer(build,"CCheck");
        // this is where you 'build' the project
        // since this is a dummy, we just say 'hello world' and call that a build

        // this also shows how you can consult the global configuration of the builder
        listener.getLogger().println("parseing the data....");

        listener.getLogger().println("generate the json data....");
        try {
            String json = buildAnalyzer.generateMappedJsonResult(build, this);
            listener.getLogger().println("finished parase .");

            listener.getLogger().println("start sending the data to the remote server at " + this.getDescriptor().getRemoteEndPoint());

            Map<String, String> result = new HashMap<String, String>();


            result = getResultFromRemoteRequest(json, listener);
            if (doSync && result != null && result.get("numbers") != null) {
                List<String> numberList = JsonUtils.convertJsonListString(result.get("numbers"));
                List<AbstractBuild> previousBuilds = build.getProject().getBuilds();
                List<String> buildsJson = generateSyncBuildsJsonList(previousBuilds, numberList);
                Thread.sleep(Long.parseLong(result.get("period")));
                sendOutBody(buildsJson, listener);
            }

        } catch (InterruptedException e) {
            listener.getLogger().println("Error paras period from server");
        } catch (NoSuchMethodException e) {
            listener.getLogger().println(jsonExceptionString);
        } catch (IllegalAccessException e) {
            listener.getLogger().println(jsonExceptionString);
        } catch (InvocationTargetException e) {
            listener.getLogger().println(jsonExceptionString);
        } catch (IOException e) {
            listener.getLogger().println(jsonExceptionString);
        } catch (InstantiationException e) {
            listener.getLogger().println(jsonExceptionString);
        }

        return true;
    }


    private Map<String, String> getResultFromRemoteRequest(String json, BuildListener listener) {
        MessageSender messageSender = MessageSenderBuilder.createMessageSenderBuilder().createJmsComponent("jms", this.getDescriptor().getRemoteEndPoint()).build();
        CamelContext camelContext = messageSender.getContext();
        Map<String, String> result = new HashMap<String, String>();
        ProducerTemplate template = camelContext.createProducerTemplate();

        try {
            template.start();
            //template.sendBody("jms:queue:xmlBody",json);
            Future<Map> future = template.asyncRequestBody("jms:queue:xmlBody", json, Map.class);
            Boolean finished = false;
            while (!finished) {
                finished = future.isDone();
                if (!finished) {
                    Thread.sleep(50);
                }
            }
            result = future.get();
            listener.getLogger().println("sending in async callback model finished .");
            template.stop();
        } catch (UncategorizedJmsException exception) {
            listener.getLogger().println(exceptionString);
        } catch (JmsException exception) {
            listener.getLogger().println(exceptionString);
        } catch (Exception e) {
            listener.getLogger().println(exceptionString);
        }

        return result;
    }


    private List<String> generateSyncBuildsJsonList(List<AbstractBuild> builds, List<String> numberList) throws InvocationTargetException, IOException, NoSuchMethodException, IllegalAccessException, InstantiationException {
        List<String> toBeSyncList = new ArrayList<String>();
        for (AbstractBuild build : builds) {
            if (!numberList.contains(String.valueOf(build.getNumber()))) {
                toBeSyncList.add(buildAnalyzer.generateMappedJsonResult(build, this));
            }
        }
        return toBeSyncList;
    }


    private void sendOutBody(List<String> jsons, BuildListener listener) {
        MessageSender messageSender = MessageSenderBuilder.createMessageSenderBuilder().createJmsComponent("jms", this.getDescriptor().getRemoteEndPoint()).build();
        CamelContext camelContext = messageSender.getContext();
        Map<String, String> result = new HashMap<String, String>();
        ProducerTemplate template = camelContext.createProducerTemplate();

        try {
            template.start();
            //template.sendBody("jms:queue:xmlBody",json);
            template.sendBody("jms:queue:bulkXML", jsons);

            listener.getLogger().println("sending in async out only model finished finished .");
            template.stop();
        } catch (UncategorizedJmsException exception) {
            listener.getLogger().println(exceptionString);
        } catch (JmsException exception) {
            listener.getLogger().println(exceptionString);
        } catch (Exception e) {
            listener.getLogger().println(exceptionString);
        }

    }


    // overrided for better type safety.
    // if your plugin doesn't really define any property on Descriptor,
    // you don't have to do this.
    @Override
    public ResultDescriptor getDescriptor() {
        return (ResultDescriptor) super.getDescriptor();
    }

    public BuildStepMonitor getRequiredMonitorService() {
        return BuildStepMonitor.STEP;
    }


    public String getHudsonUrl() {
        return hudsonUrl;
    }

    public void setHudsonUrl(String hudsonUrl) {
        this.hudsonUrl = hudsonUrl;
    }

    public String getTeamFoundation() {
        return teamFoundation;
    }

    public void setTeamFoundation(String teamFoundation) {
        this.teamFoundation = teamFoundation;
    }

    public String getIcafeProject() {
        return icafeProject;
    }

    public void setIcafeProject(String icafeProject) {
        this.icafeProject = icafeProject;
    }

    public String getIcafeModule() {
        return icafeModule;
    }

    public void setIcafeModule(String icafeModule) {
        this.icafeModule = icafeModule;
    }

    public String getIcafeVersion() {
        return icafeVersion;
    }

    public void setIcafeVersion(String icafeVersion) {
        this.icafeVersion = icafeVersion;
    }

    public String getViewUrl() {
        return viewUrl;
    }

    public void setViewUrl(String viewUrl) {
        this.viewUrl = viewUrl;
    }

    public String getViewName() {
        return viewName;
    }

    public void setViewName(String viewName) {
        this.viewName = viewName;
    }

    public String getProjectType() {
        return projectType;
    }

    public void setProjectType(String projectType) {
        this.projectType = projectType;
    }

    public String getLocalServername() {
        return localServername;
    }

    public void setLocalServername(String localServername) {
        this.localServername = localServername;
    }

    public boolean isDoSync() {
        return doSync;
    }

    public void setDoSync(boolean doSync) {
        this.doSync = doSync;
    }
}

